#include <stdlib.h>
#include <stdio.h>
#include <string.h>

typedef struct {
	int sig;
	unsigned long int size;
	unsigned short int r1, r2;
	unsigned int offset;
} Header;

typedef struct {
	unsigned int infohsize;
	int width, height;
	unsigned short int num_planes;
	unsigned short int bits;
	unsigned int compress;
	unsigned int image_size;
	int hres, vres;
	unsigned int num_colours;
	unsigned int num_nicecolours;
} info_header;

typedef struct {
    Header header;
	info_header info_header;
	char **B;
	char **G;
	char **R;
} Bmp_Image;

int ReadBitmapHeader(FILE *fd, Bmp_Image *bmp) {

	fread(&bmp->header.sig, 1, 2, fd);

	if(bmp->header.sig != 19778) {
	    printf("This is not a bitmap file!\n");
		return 1;
	}
    fread(&bmp->header.size, 1, 4, fd);
	fread(&bmp->header.r1, 1, 2, fd);
	fread(&bmp->header.r2, 1, 2, fd);
	fread(&bmp->header.offset, 1, 4, fd);

	fread(&bmp->info_header.infohsize, 1, 4, fd);

    if(bmp->info_header.infohsize != 40) {
        printf("This is not a Windows version 3 bitmap image!\n");
		return 2;
	}
    fread(&bmp->info_header.width, 1, 4, fd);
	fread(&bmp->info_header.height, 1, 4, fd);
	fread(&bmp->info_header.num_planes, 1, 2, fd);
	fread(&bmp->info_header.bits, 1, 2, fd);

	if(bmp->info_header.bits != 24) {
	    printf("This bitmap file isn't a 24-bits-per-pixel image!\n");
		return 3;
	}

	fread(&bmp->info_header.compress, 1, 4, fd);

	if(bmp->info_header.compress != 0) {
	    printf("This bitmap image has compress!\n");
		return 4;
	}

	fread(&bmp->info_header.image_size, 1, 4, fd);
	fread(&bmp->info_header.hres, 1, 4, fd);
	fread(&bmp->info_header.vres, 1, 4, fd);
	fread(&bmp->info_header.num_colours, 1, 4, fd);
	fread(&bmp->info_header.num_nicecolours, 1, 4, fd);

    if(bmp->info_header.image_size == 0) {
        printf("This bitmap doesn't have an image data size");
        return 5;
    }

	return 0;
}

int ReadBitmapData(FILE *fd, Bmp_Image *bmp) {
	int i, j;

	fseek(fd, 0, SEEK_SET);
	fseek(fd, bmp->header.offset, SEEK_CUR); /* Inicio da imagem */
	bmp->B = (char**)calloc(bmp->info_header.width*sizeof(char*));
	bmp->G = (char**)calloc(bmp->info_header.width*sizeof(char*));
	bmp->R = (char**)calloc(bmp->info_header.width*sizeof(char*));
	for(i = 0; i < bmp->info_header.width; i++) {
	    bmp->B[i] = (char*)calloc(bmp->info_header.height*sizeof(char));
	    bmp->G[i] = (char*)calloc(bmp->info_header.height*sizeof(char));
	    bmp->R[i] = (char*)calloc(bmp->info_header.height*sizeof(char));

	}

	for(i = 0; i < bmp->info_header.height; i++) {
		for(j = 0; j < bmp->info_header.width; j++) {
			fread(&bmp->B[j][i], 1, 1, fd);
			fread(&bmp->G[j][i], 1, 1, fd);
			fread(&bmp->R[j][i], 1, 1, fd);
		}
		fseek(fd, bmp->info_header.width % 4, SEEK_CUR);
	}


	return 0;
}

void WriteBitmapHeader(FILE *fd, Bmp_Image *bmp) {

	fwrite(&bmp->header.sig, 1, 2, fd);

    fwrite(&bmp->header.size, 1, 4, fd);
	fwrite(&bmp->header.r1, 1, 2, fd);
	fwrite(&bmp->header.r2, 1, 2, fd);
	fwrite(&bmp->header.offset, 1, 4, fd);

	fwrite(&bmp->info_header.infohsize, 1, 4, fd);

    fwrite(&bmp->info_header.width, 1, 4, fd);
	fwrite(&bmp->info_header.height, 1, 4, fd);
	fwrite(&bmp->info_header.num_planes, 1, 2, fd);
	fwrite(&bmp->info_header.bits, 1, 2, fd);

	fwrite(&bmp->info_header.compress, 1, 4, fd);

	fwrite(&bmp->info_header.image_size, 1, 4, fd);
	fwrite(&bmp->info_header.hres, 1, 4, fd);
	fwrite(&bmp->info_header.vres, 1, 4, fd);
	fwrite(&bmp->info_header.num_colours, 1, 4, fd);
	fwrite(&bmp->info_header.num_nicecolours, 1, 4, fd);

}

int WriteBitmapImage(char *filename_out, Bmp_Image *bmp) {
	unsigned short int end_of_line = '\00';
	int i, j, k;
	FILE *out;

	out = fopen(filename_out, "wb");
	if(out == NULL) {
	    printf("Failed at opening the file for writing!\n");
		fclose(out);
		return 1;
	}

	WriteBitmapHeader(out, bmp);
	
	for(i = 0; i < bmp->info_header.height; i++) {
		for(j = 0; j < bmp->info_header.width; j++) {

			fwrite(&bmp->B[j][i], 1, 1, out);
			fwrite(&bmp->G[j][i], 1, 1, out);
			fwrite(&bmp->R[j][i], 1, 1, out);
		}
		for(k = 0; k < (bmp->info_header.width % 4); k++) {
			fwrite(&end_of_line, 1, 1, out);
		}
	}

	fclose(out);
	return 0;
}
